<?php
/**
 * Project: firework-cms             >>  http://firework-cms.googlecode.com
 * Authors: YouniS Ben Salah  (-.-)  >>  http://younishd.kegtux.org
 *          Fabien Wang       (^.^)  >>  http://fabienwang.fr
 * License: See README and LICENSE in the top level directory
 */
 
/**
 * Module: ConfParser [CP]
 */
 
/**
 * Class: ConfParser
 * Represents the ConfParser module [CP]
 * Allows parsing in both directions ( file<-->config )
 * Interface:
 *      set( key, value, [comment] )
 *      permanently writes a config couple ( key=value ) in config file.
 *      comment is optional.
 * 
 *      get( key )
 *      returns value of key by reading from a config file.
 * 
 *      load( filename )
 *      sets the config filename ( with full dir path ) to be used by the parser.
 * 
 *      update()
 *      reloads the items by re-parsing the config file - useful when config file gets modified at runtime or after loading a different config file with load().
 * 
 *      drop( key )
 *      permanently removes a config couple from config file.
 * 
 * TODO:
 * Implement array feature ( interface methods accepting arrays of key value couples ).
 * Recognize int values and store them as ints instead of strings.
 */
class ConfParser
{
    // ctor
    public function __construct( $filename = NULL )
    {
        $this->items = array();
        $this->new_items = false;
        if ( $filename != NULL ) {
            $this->load($filename);
        }
        else {
            $this->load('config/appconf.php');
        }
        $this->parse();
    }
    
    /**
     * Method: set()
     */
    public function set( $key, $value = '', $comment = NULL )
    {
        // if key exists in items, set comment ( optional ) and add to new_items.
        if ( !$this->exists($key) ) { if ( !isset($comment) ) { $comment = $key; } $this->new_items[$key] = $comment; }
        // then add to items ( making sure value is a string ) and save.
        $this->items[$key] = "$value";
        $this->save();
    }
    
    /**
     * Method: drop()
     */
    public function drop( $key )
    {
        // unset the key in items array and save changes.
        unset($this->items[$key]);
        $this->save();
    }
    
    /**
     * Method: load()
     */
    public function load( $filename )
    {
        if ( file_exists($filename) ) {
            $this->filename = $filename;
        }
        else {
            throw new Exception('parser failed to load config file: '.$filename);
        }
    }
    
    /**
     * Method: update()
     */
    public function update()
    {
        // as simple as this
        $this->parse();
    }
    
    /**
     * Method: get()
     */
    public function get( $key )
    {
        if ( isset($this->items[$key]) ) {
            return $this->items[$key];
        }
        else {
            throw new Exception('parser failed to get key '.$key.' from items array: no such key.');
        }
    }
    
    
    /**
     * Method: parse()
     * parses config file and stores items in array.
     */
    protected function parse()
    {
        if ( !$filestream = fopen( $this->filename, 'rb' ) ) { throw new Exception('parser failed to open config file: '.$this->filename); }
        fgets($filestream); // skip the php tags
        while ( $line = fgets($filestream) ) {
            $matches = array();
            // if current line is not a comment or blank..
            if ( !preg_match( '#(^/{2})|(^\s$)#', $line ) ) {
                // ..store the found item in array
                preg_match( '#^(.*?)=(.*?)$#', $line, $matches );
                $this->items[$matches[1]] = $matches[2];
            }
        }
        fclose($filestream);
    }
    
    /**
     * Method: save()
     * Permanently writes the changes to file.
     */
    protected function save()
    {
        $conf = "<?php die(); ?>\n"; // to prohibit reading the config file
        if ( !$filestream = fopen( $this->filename, 'rb' ) ) { throw new Exception('parser failed to open config file: '.$this->filename); }
        fgets($filestream); // skip the php tags
        while ( $line = fgets($filestream) ) {
            $matches = array();
            // if current line is not a comment or blank..
            if ( !preg_match( '#(^/{2})|(^\s$)#', $line ) ) {
                // ..append key=value to conf string
                preg_match( '#^(.*?)=(.*?)$#', $line, $matches );
                // only write if key is set, else ignore that line - means that config has been removed ( or stolen by ninjas ).
                if ( isset($this->items[$matches[1]]) ) {
                    $conf .= $matches[1]."=".$this->items[$matches[1]]."\n";
                }
            }
            else {
                // append comment line as is.
                $conf .= $line;
            }
        }
        fclose($filestream);
        // check for new items
        if ( $this->new_items ) {
            foreach( $this->new_items as $key => $comment ) {
                $conf .= "\n// ".$comment."\n";
                $conf .= $key."=".$this->items[$key]."\n";
            }
            $this->new_items = false;
        }
        // write built conf to file
        $filestream = fopen( $this->filename, 'wb' );
        fwrite( $filestream, $conf );
        fclose($filestream);
    }
    
    /**
     * Method: exists()
     * returns true if key does exist or false if not.
     */
    protected function exists( $key )
    {
        //$found = false;
        //if ( $filestream = fopen( $this->filename, 'rb' ) ) {
            //fgets($filestream); // skip the opening php tag
            //while ( ($line = fgets($filestream)) && !$found ) {
                //if ( preg_match( '#^'.$key.'=#', $line ) ) { $found = true; }
            //}
            //fclose($filestream);
        //}
        //else {
            //throw new Exception('parser failed to open config file: '.$this->filename);
        //}
        //return $found;
        return array_key_exists( $key, $this->items );
    }

    // config filename ( with full dir path )
    protected $filename;
    
    // array containing config items ( key value couples )
    protected $items;
    
    // array of new config items ( key comment couple )
    // will be reset to false once written into file
    protected $new_items;
}

/**
 * Class: ListConfParser
 * IS_A ConfParser
 * Parses simple lists.
 */
class ListConfParser extends ConfParser
{
    // ctor
    public function __construct( $filename = NULL )
    {
        $this->items = array();
        $this->new_items = false;
        if ( $filename != NULL ) {
            $this->load($filename);
            $this->parse();
        }
        else {
            $this->load('config/pages.php');
            $this->parse();
        }
    }
    
    /**
     * Method: drop()
     */
    public function drop( $value )
    {
        // unset and save.
        foreach ( $this->items as $key => $value ) {
            if ( $this->items[$key] == $value ) {
                unset($this->items[$key]);
            }
        }
        $this->save();
    }
    
    /**
     * Method: set()
     * the key is considered as value here. value and comment are ignored.
     */
    public function set( $key, $value = '', $comment = NULL )
    {
        if ( !$this->exists($key) ) {
            $this->items[] = $key;
        }
        $this->save();
    }
    
    
    /**
     * Method: save()
     * Permanently writes the changes to file.
     */
    protected function save()
    {
        $conf = "<?php die(); ?>\n"; // to prohibit reading the config file
        if ( !$filestream = fopen( $this->filename, 'rb' ) ) { throw new Exception('parser failed to open config file: '.$this->filename); }
        fgets($filestream); // skip the php tags
        while ( $line = fgets($filestream) ) {
            $matches = array();
            // if current line is not a comment or blank..
            if ( !preg_match( '#(^/{2})|(^\s$)#', $line ) ) {
                // ..append line value to conf string
                preg_match( '#^(.*?)$#', $line, $matches );
                // only write if value is in items array, else ignore that line - means that line has been removed ( or stolen by ninjas ).
                if ( in_array( $matches[1], $this->items ) ) {
                    $conf .= $matches[1]."\n";
                }
            }
            else {
                // append comment line as is.
                $conf .= $line;
            }
        }
        fclose($filestream);
        // check for new items
        if ( $this->new_items ) {
            foreach( $this->new_items as $value ) {
                $conf .= "\n// ".$comment."\n";
                $conf .= $value."\n";
            }
            $this->new_items = false;
        }
        // write built conf to file
        $filestream = fopen( $this->filename, 'wb' );
        fwrite( $filestream, $conf );
        fclose($filestream);
    }

    /**
     * Method: parse()
     * parses config file and stores items in array.
     */
    protected function parse()
    {
        if ( !$filestream = fopen( $this->filename, 'rb' ) ) { throw new Exception('parser failed to open config file: '.$this->filename); }
        fgets($filestream); // skip the php tags
        while ( $line = fgets($filestream) ) {
            $matches = array();
            // if current line is not a comment or blank..
            if ( !preg_match( '#(^/{2})|(^\s$)#', $line ) ) {
                // ..store the found item in array
                preg_match( '#^(.*?)$#', $line, $matches );
                $this->items[] = $matches[1];
            }
        }
        fclose($filestream);
    }
    
    /**
     * Method: get()
     */
    public function get( $line = 0 )
    {
        $line = (int) $line;
        if ( $line > 0 ) {
            return $this->items[$line];
        }
        else {
            return $this->items;
        }
    }
    
    /**
     * Method: exists()
     * returns true if value does exist or false if not.
     */
    //protected function exists( $value )
    //{
        //return in_array( $value, $this->items );
    //}
    
    /**
     * Method: throwEx()
     * best method ever.
     * throw ex ( giving reason is optional ).
     */
    //protected function throwEx( $reason = NULL )
    //{
        //throw new Exception($reason);
    //}
}
